﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Web.Mvc;

using Fuse8.DomainFramework.Common;
using Fuse8.ContentManagementFramework.SystemFacade;

namespace Fuse8.ContentManagementFramework.Web.MvcPowered
{
    /// <summary>
    /// Default control builder
    /// </summary>
    /// <typeparam name="TComponent">Concrete control</typeparam>
    /// <typeparam name="TModel">Control model</typeparam>
    public class CmsMvcBaseComponent<TComponent, TModel> : IHtmlString
        where TComponent : class        
    {
        private bool _noWrapper;

        /// <summary>
        /// Instance of current HtmlHelper
        /// </summary>
        public HtmlHelper Html { get; set; }

        /// <summary>
        /// Gets wrapper element name
        /// </summary>
        public string NameAttribute { get; protected set; }

        /// <summary>
        /// Template for render
        /// </summary>
        protected Func<TModel, object> InlineTemplate { get; set; }

        /// <summary>
        /// Instance of concrete component
        /// </summary>
        protected TComponent Component { get; set; }
        
        /// <summary>
        /// Wrapper HTML attributes
        /// </summary>
        protected IDictionary<string, object> HtmlAttributes { get; private set; }

        /// <summary>
        /// Component model
        /// </summary>
        protected virtual TModel Value { get; set; }

        /// <summary>
        /// Wrapper element tag
        /// </summary>
        protected string WrapperElement { get; private set; }

        /// <summary>
        /// Indicates that component can't be rendered
        /// </summary>
        protected bool CancelRender { get; set; }

        /// <summary>
        /// Initializes base component
        /// </summary>
        /// <param name="htmlHelper"></param>
        public CmsMvcBaseComponent(HtmlHelper htmlHelper)
        {
            Guard.ArgumentNotNull(htmlHelper, "htmlHelper");

            Html = htmlHelper;            
        }

        /// <summary>
        /// Specifies component wrapper
        /// </summary>
        /// <param name="name">Wrapper name</param>
        /// <param name="tag">Wrapper tag</param>
        /// <param name="htmlAttributes">Wrapper HTML attributes</param>
        /// <returns></returns>
        public TComponent Wrapper(string name = null, string tag = "div", object htmlAttributes = null)
        {
            _noWrapper = false;
            if (!string.IsNullOrEmpty(tag))
            {
                WrapperElement = tag;               
            }
            if (!string.IsNullOrEmpty(name))
            {
                NameAttribute = name;
            }
            if (htmlAttributes != null)
            {
                HtmlAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
            }
            return Component;
        }

        /// <summary>
        /// Sets wrapper HTML attributes. If wrapper element is not defined, sets it to DIV
        /// </summary>
        /// <param name="value">Attributes</param>
        /// <returns></returns>
        /// <remarks>Overrides previously set value</remarks>
        public TComponent Attributes(object value)
        {
            if (value != null)
            {
                HtmlAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(value);
                EnsureWrapperElement();
            }
            return Component;
        }

        /// <summary>
        /// Stops using wrapper tag and its attributes
        /// </summary>
        /// <returns></returns>
        public TComponent NoWrapper()
        {
            _noWrapper = true;
            WrapperElement = "";
            return Component;
        }

        /// <summary>
        /// Sets wrapper element name. If wrapper element is not defined, sets it to DIV
        /// </summary>
        /// <param name="name">Element name</param>
        /// <returns></returns>
        /// <remarks>Overrides previously set value</remarks>
        public TComponent Name(string name)
        {
            if (!string.IsNullOrEmpty(name))
            {
                NameAttribute = name;
                EnsureWrapperElement();
            }
            return Component;
        }

        /// <summary>
        /// Specifies template for render
        /// </summary>
        /// <param name="template">Template function</param>
        /// <returns></returns>
        public TComponent Template(Func<TModel, object> template)
        {
            if (template != null)
            {
                InlineTemplate = template;
            }

            return Component;
        }

        /// <summary>
        /// Renders component as MvcHtmlString.
        /// </summary>
        /// <returns></returns>
        public MvcHtmlString Render()
        {
            if (CancelRender)
            {
                return MvcHtmlString.Empty;
            }
            using (var output = new StringWriter())
            {
                if (!Html.ViewContext.TempData.ContainsKey(CmsConfiguration.ContextTemplateKey))
                {
                    using (HtmlTextWriter textWriter = new HtmlTextWriter(output))
                    {
                        textWriter.Write(RenderComponent());
                    }
                }

                return MvcHtmlString.Create(output.ToString());
            }
        }

        /// <summary>
        /// Calls InlineTemplate function
        /// </summary>
        /// <returns></returns>
        protected virtual string RenderTemplate()
        {
            return InlineTemplate(Value).ToString(); //TO DO: think about SetInnerHtml() method, but here is html encoding issue
        }
        
        /// <summary>
        /// Renders component HTML
        /// </summary>
        /// <returns></returns>
        protected virtual string RenderComponent()
        {
            string result;
            string innerHtml = "";
            if ((InlineTemplate != null) && (Value != null))
            {
                
                innerHtml = RenderTemplate();
            }

            if (string.IsNullOrEmpty(WrapperElement))
            {
                result = innerHtml;
            }
            else
            {
                var tagBuilder = new TagBuilder(WrapperElement);

                string fullName = Html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(NameAttribute);
                if (!string.IsNullOrEmpty(fullName))
                {
                    tagBuilder.GenerateId(fullName);                    
                }

                if (HtmlAttributes != null)
                {
                    tagBuilder.MergeAttributes(HtmlAttributes, true);
                }

                tagBuilder.InnerHtml = innerHtml;                
                result = tagBuilder.ToString(TagRenderMode.Normal);
            }

            return result;
        }

        /// <summary>
        /// Presents component as control string
        /// </summary>
        /// <returns></returns>
        public string ToHtmlString()
        {
            return Render().ToHtmlString();
        }

        private void EnsureWrapperElement()
        {
            if (string.IsNullOrEmpty(WrapperElement) && !_noWrapper)
            {
                WrapperElement = "div";
            }
        }
    }
}
